官方 Demo:https://vueuse.org/core/useIntersectionObserver/#useintersectionobserver
這個原始碼的核心是 IntersectionObserver,主要可以透過這個 Web API 來得知目標元素是否出現在 root
的可視範圍中,以及在可視範圍中的狀態。因為是非同步執行,可以避免在主執行序上做偵測與計算。
參數部分提幾個等等會用到的:
callback
:當目標元素可視區域時,要執行的 callback function。options
root
:指定根元素,預設值為瀏覽器 view port,以 Demo 為例,root 就是外層那個 scroll container。rootMargin
:類似 CSS margin,用於擴大或縮小根元素的有效範圍。以 Demo 為例,如果 rootMargin
設定 100px,那在元素進入可視區域之前就會被檢測到。threshold
:目標元素可視區域達到什麼比例時觸發 callback function。[0, 0.5, 1]
的話,在 callback function 中拿到 entry.intersectionRatio 判斷當前的比例,可以做更細緻的效果。細節可參考 mdn:https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
target
:要偵測的目標元素,可以是 Array。callback
:同 IntersectionObserver Web API。options
:
root
:同 IntersectionObserver Web API。rootMargin
:同 IntersectionObserver Web API。threshold
:同 IntersectionObserver Web API。window
:瀏覽器環境預設為 window
。immediate
:是否要馬上呼叫 IntersectionObserver Web API 對 target 進行觀察。// src/compositions/useIntersectionObserver.js
import { computed, ref, watch } from 'vue'
import { defaultWindow, noop, notNullish, toValue, unrefElement } from '@/helper'
import { tryOnScopeDispose } from '@/utils/shared'
import { useSupported } from '@/compositions/useSupported'
export function useIntersectionObserver(target, callback, options = {}) {
const {
root,
rootMargin = '0px',
threshold = 0.1,
window = defaultWindow,
immediate = true,
} = options
const isSupported = useSupported(() => window && 'IntersectionObserver' in window)
const targets = computed(() => {
const _target = toValue(target)
return (Array.isArray(_target) ? _target : [_target]).map(unrefElement).filter(notNullish)
})
let cleanup = noop
const isActive = ref(immediate)
const stopWatch = isSupported.value
? watch(
() => [targets.value, unrefElement(root), isActive.value],
([targets, root]) => {
cleanup()
if (!isActive.value)
return
if (!targets.length)
return
const observer = new IntersectionObserver(
callback,
{
root: unrefElement(root),
rootMargin,
threshold,
},
)
targets.forEach(el => el && observer.observe(el))
cleanup = () => {
observer.disconnect()
cleanup = noop
}
},
{ immediate, flush: 'post' },
)
: noop
const stop = () => {
cleanup()
stopWatch()
isActive.value = false
}
tryOnScopeDispose(stop)
return {
isSupported,
isActive,
pause() {
cleanup()
isActive.value = false
},
resume() {
isActive.value = true
},
stop,
}
}
這個 API 原始碼的結構跟之前 Day8 ~ Day10 看到的 useEventListener 有點相似,Day8 也有提到,在組件註銷前需要透過 tryOnScopeDispose(stop)
來 stopWatch 的部分,這裡就先不多談。
先看 isSupported
,瀏覽器不支援 IntersectionObserver 的話就不用玩了 XD,到 caniuse 會發現主流瀏覽器都有支援,所以可以放心使用。
核心邏輯在 watch 內,來看看有哪些方式可以觸發這個 watch callback function。
immediate
為 true
,組件掛載後馬上執行。immediate
為 false
,因為有 return resume
出去,在外層執行 resume
時,isActive
這個 ref 會變成 true
,isActive
是這個 watch 觀察的物件,所以變化的時候會執行 watch callback function。要如何暫停 IntersectionObserver API 的觀察?return 出去的物件中有一個 pause
:
pause() {
cleanup()
isActive.value = false
},
執行 cleanup
的時候,會透過 observer.disconnect()
來執行 IntersectionObserver API 的 disconnect
方法,這招就可以停止觀察。
接著來看核心:
// src/compositions/useIntersectionObserver.js
// ...略
const observer = new IntersectionObserver(
callback,
{
root: unrefElement(root),
rootMargin,
threshold,
},
)
targets.forEach(el => el && observer.observe(el))
// ...略
使用 IntersectionObserver Web API 來偵測 target(s) 在 root 元素中的狀態,target(s) 進到可視範圍中,觸發我們傳入的 callback
。
來看看 Demo code 怎麼使用 callback 的:
<!-- src/components/UseIntersectionObserverDemo.vue -->
<script setup>
// ...略
const { isActive, pause, resume } = useIntersectionObserver(
target,
([{ isIntersecting }]) => {
isVisible.value = isIntersecting
},
{ root },
)
</script>
可以看到 callback 中透過解構拿到 IntersectionObserver Web API 給我們的 isIntersecting
,isIntersecting 為 true 代表 target 進到可視範圍中。
callback function 的其他參數們可以參考 mdn:https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry/isIntersecting
整個流程差不多就是這樣,看完覺得 IntersectionObserver Web API 真的是個厲害的角色,vueuse 也把它封裝成更方便使用的版本,如果要做更細緻的功能,也可以透過 callback function 拿到更多 IntersectionObserver Web API 參數來處理,滿喜歡這種設計。
GitHub PR:https://github.com/RhinoLee/30days_vue/pull/22/files
useIntersectionObserver API 就到這邊告一段落啦~
明天繼續看 useElementVisibility API,其實本來今天就要看了 XD 只是突然發現原來 useElementVisibility 的核心是 useIntersectionObserver。